package main

import (
	"flag"
	"fmt"
	"os"
	"runtime"
	"time"
)

const Size1M = 1024 * 1024

var n = flag.Int("n", 1e5, "Number of goroutines to create")

var ch = make(chan byte)
var counter = 0

func f() {
	counter++
	ch <- 'a' // Block this goroutine
	fmt.Println("Error")
}

func main() {
	flag.Parse()
	if *n <= 0 {
		fmt.Fprintf(os.Stderr, "invalid number of goroutines")
		os.Exit(1)
	}

	fmt.Println("*n", *n)

	// Limit the number of spare OS threads to just 1
	runtime.GOMAXPROCS(1)

	// Make a copy of MemStats
	m0 := runtime.MemStats{}
	runtime.ReadMemStats(&m0)

	t0 := time.Now().Nanosecond()
	for i := 0; i < *n; i++ {
		go f()
	}

	for {
		if counter != *n {
			runtime.Gosched()
			// fmt.Fprintf(os.Stderr, "failed to begin execution of all goroutines. %d, %d", counter, *n)
			// os.Exit(1)
		} else {
			break
		}
	}

	runtime.Gosched()
	t1 := time.Now().Nanosecond()
	runtime.GC()

	// Make a copy of MemStats
	m1 := runtime.MemStats{}
	runtime.ReadMemStats(&m1)

	if counter != *n {
		fmt.Fprintf(os.Stderr, "failed to begin execution of all goroutines. %d, %d", counter, *n)
		os.Exit(1)
	}

	fmt.Printf("Number of goroutines: %d\n", *n)
	fmt.Printf("Per goroutine:\n")
	fmt.Printf("  Memory: %.2f bytes\n", float64(m1.Sys-m0.Sys)/float64(*n))
	fmt.Printf("  Time:   %f µs\n", float64(t1-t0)/float64(*n)/1e3)
	fmt.Printf("\nTotal(MB):\n")
	fmt.Println("  Memory Total:", m0.Sys/Size1M, "MB", m1.Sys/Size1M, "MB")
	fmt.Println("  Alloc Total:", m0.Alloc/Size1M, "MB", m1.Alloc/Size1M, "MB")
	fmt.Println("  Heap Total:", m0.HeapAlloc/Size1M, "MB", m1.HeapAlloc/Size1M, "MB")
}

// Output:
//
// Number of goroutines: 100000
// Per goroutine:
//   Memory: 4243.45 bytes
//   Time:   5.815950 µs

// leomac:goroutine milliyang$ ./goroutine -n 400000
// *n 400000
// Number of goroutines: 400000
// Per goroutine:
//   Memory Total: 4200680 3439175928
//   Alloc Total: 189952 119587936
//   Heap Total: 189952 119587936
//   Memory: 8587.44 bytes
//   Time:   1.354716 µs
